﻿using LogSystem;
using System.Collections.Generic;
using UnityEngine;
using Yoozoo.Managers.ResourceManagerV2.Runtime;

namespace Yoozoo.Gameplay.RTS
{
    public class GPUInstancingManager
    {
        public static string CityConfigAssetPath = "Assets/ResourcesAssets/City/GpuSkinning/Common.asset";  
        public static string CommonConfigAssetPath = CityConfigAssetPath;

        private Dictionary<int, int> m_dic = new Dictionary<int, int>();
        private Dictionary<int, UEInstanceAnimation> m_instancingMap = new Dictionary<int, UEInstanceAnimation>();

        private Dictionary<int, int> m_gpuDic = new Dictionary<int, int>();
        private Dictionary<int, GpuInstanceGroup> m_gpuInstancingMap = new Dictionary<int, GpuInstanceGroup>();

        //private Dictionary<int, Texture2D> m_animationTex =
        //    new Dictionary<int, Texture2D>();

        private GPUSkinning_AllAnimation allAnimation;
        private Texture2D animationTex;

        private static GPUInstancingManager m_instance;
        
        private Dictionary<string,Texture2D> m_animationTexDic = new Dictionary<string, Texture2D>();
        
        public static bool isSurvivor = false;
        
#if DEBUG_GPU_SKINNING
        public bool drawGUI = true;
#else
        public bool drawGUI = false;
#endif

        public Texture2D AnimationTex => animationTex;

        public GPUSkinning_AllAnimation AllAnimation => allAnimation;

        public static GPUInstancingManager GetInstance()
        {
            if (m_instance != null) return m_instance;
            return m_instance = new GPUInstancingManager();
        }
        
        public static bool IsInstanceExist()
        {
            return m_instance != null;
        }

        public static void SetCityPath()
        {
            CommonConfigAssetPath = CityConfigAssetPath;
        }
        
        public void Initialize()
        {
#if UNITY_EDITOR
            if (!UMTResource.Initializated)
            {
                var gpuSkinningAllAnimation = UnityEditor.AssetDatabase.LoadAssetAtPath<GPUSkinning_AllAnimation>(CommonConfigAssetPath);
                animationTex = LoadBoneAnimation(gpuSkinningAllAnimation);
                return;
            }
#endif
            UMTResource.LoadAsset(CommonConfigAssetPath, (name, asset, duration, data) =>
                {
                    if(asset is GPUSkinning_AllAnimation gpuSkinningAllAnimation)
                        animationTex = LoadBoneAnimation(gpuSkinningAllAnimation);
                });
        }

        public bool IsInitialized()
        {
            return allAnimation != null && animationTex != null;
        }

        public void Dispose()
        {
            if (AnimationTex)
            {
                UnityEngine.Object.Destroy(AnimationTex);
            }

            foreach (var item in m_instancingMap)
            {
                for (int i = 0; i < item.Value.materials.Length; i++)
                {
                    if (item.Value.materials[i])
                    {
                        UnityEngine.Object.Destroy(item.Value.materials[i]);
                    }
                }

                item.Value.DestroyTexture();
                item.Value.Dispose();
            }

            foreach (var item in m_gpuInstancingMap)
            {
                item.Value.Dispose();
            }
            
            m_animationTexDic.Clear();

            m_instance = null;
        }

        public void Update()
        {
            rendererTotal = 0;
            animatorTotal = 0;
            foreach (var item in m_instancingMap)
            {
                var ueInstanceAnimation = item.Value;
                
                rendererTotal += ueInstanceAnimation.RendererCount;
                animatorTotal += ueInstanceAnimation.AnimatorCount;
                ueInstanceAnimation.Update();
            }

            foreach (var item in m_gpuInstancingMap)
            {
                item.Value.Update();
            }
#if UWAp
            UWAEngine.LogValue("GpuInstancing_RendererCount", rendererTotal);
            UWAEngine.LogValue("GpuInstancing_AnimatorCount", animatorTotal);
#endif
        }

        private List<int> m_pauseList = new List<int>();

        public void Pause(int id)
        {
            if (!m_pauseList.Contains(id))
            {
                m_pauseList.Add(id);
            }
        }

        public void Resume(int id)
        {
            if (m_pauseList.Contains(id))
            {
                m_pauseList.Remove(id);
            }
        }

        /// <summary>
        /// 动画数据转成Texture2D,并存储
        /// </summary>
        /// <param name="allAnimation"></param>
        /// <returns></returns>
        public Texture2D LoadBoneAnimation(GPUSkinning_AllAnimation allAnimation)
        {
            this.allAnimation = allAnimation;
            
#if UNITY_EDITOR
            var textAsset = UnityEditor.AssetDatabase.LoadAssetAtPath<TextAsset>(allAnimation.textureDataPath);
            var tex2D = new Texture2D(allAnimation.textureWidth, allAnimation.textureHeight,
                TextureFormat.RGBAHalf, false);
            tex2D.name = "GPUSkinTex-" + allAnimation.name;
            tex2D.filterMode = FilterMode.Point;
            tex2D.LoadRawTextureData(textAsset.bytes);
            tex2D.Apply(false,true);
#else
            var Buffer = UMTResource.Buffer;
            UMTResource.LoadBinaryFromFileSystem(allAnimation.textureDataPath, Buffer);
           
            var tex2D = new Texture2D(allAnimation.textureWidth, allAnimation.textureHeight,
                TextureFormat.RGBAHalf, false);
            tex2D.name = "GPUSkinTex-" + allAnimation.name;
            tex2D.filterMode = FilterMode.Point;
            var data = System.Runtime.InteropServices.Marshal.UnsafeAddrOfPinnedArrayElement(Buffer.Bytes, 0);
            tex2D.LoadRawTextureData(data, Buffer.Length);
            tex2D.Apply(false,true);
#endif
            return tex2D;
        }

        private static int nextInstanceId = 0;
        private static int nextGpuInstanceId = 0;

        private static int GetNextInstanceId()
        {
            nextInstanceId++;
            return nextInstanceId;
        }
        
        private static int GetNextGpuInstanceId()
        {
            nextGpuInstanceId++;
            return nextGpuInstanceId;
        }

        public static Texture2D GetInstancingTexture(GPUSkinning_AllAnimation animation)
        {
            var path = animation.textureDataPath;
            if (m_instance.m_animationTexDic.TryGetValue(path, out var texture))
            {
                return texture;
            }

            texture = m_instance.LoadBoneAnimation(animation);
            m_instance.m_animationTexDic.Add(path, texture);
            return texture;
        }

        public static void RemoveInstancingTexture(string path)
        {
            if (m_instance.m_animationTexDic.TryGetValue(path, out var texture))
            {
                Object.Destroy(texture);
                m_instance.m_animationTexDic.Remove(path);
            }
        }
        
        public static int AddInstancingConfig(int configId, GPUSkinning_AllBoneAnimation data, Material mat,
            Mesh medium, Mesh low, Material shadowMat, Color campColor,GPUSkinning_AllAnimation animation = null)
        {
            if (m_instance.m_dic.TryGetValue(configId, out var instancingId))
            {
                return instancingId;
            }

            instancingId = GetNextInstanceId();
            m_instance.m_dic.Add(configId, instancingId);

            var ue = new UEInstanceAnimation();
            ue.AddData(configId, data, mat, medium, low, shadowMat, campColor,animation);
            if (animation != null)
            {
                ue.enableDissolve = true;
                ue.enableShadowColor = true;
                ue.enableShadowHeight = true;
            }
            else
            {
                ue.enableDissolve = false;
                ue.enableShadowColor = false;
                ue.enableShadowHeight = false;
            }

            m_instance.m_instancingMap.Add(instancingId, ue);
            return instancingId;
        }

        public static int AddGpuInstancingConfig(int configId, Material mat, Mesh mesh, Material shadowMat)
        {
            if (m_instance.m_gpuDic.TryGetValue(configId, out var instancingId))
            {
                return instancingId;
            }

            instancingId = GetNextGpuInstanceId();
            m_instance.m_gpuDic.Add(configId, instancingId);
            
            var gpu = new GpuInstanceGroup();
            gpu.AddData(configId, mat, mesh, shadowMat);
            
            m_instance.m_gpuInstancingMap.Add(instancingId, gpu);
            return instancingId;
        }

        public static Material GetMainMaterial(int configId)
        {
            if (m_instance == null || m_instance.m_dic == null)
            {
                return null;
            }
            if (m_instance.m_dic.TryGetValue(configId, out var instancingId))
            {
                if (m_instance.m_instancingMap.TryGetValue(instancingId, out var ue))
                {
                    return ue.GetMainMaterial();
                }
            }
            return null;
        }

        public static Material GetShadowMaterial(int configId)
        {
            if (m_instance == null || m_instance.m_dic == null)
            {
                return null;
            }
            if (m_instance.m_dic.TryGetValue(configId, out var instancingId))
            {
                if (m_instance.m_instancingMap.TryGetValue(instancingId, out var ue))
                {
                    return ue.GetShadowMaterial();
                }
            }
            return null;
        }

        public static void SetMainColor(int instancingId, Color color)
        {
            if (m_instance.m_instancingMap.ContainsKey(instancingId))
            {
                m_instance.m_instancingMap[instancingId].SetMainColor(color);
            }
        }

        public static void SetModelLevel(UEInstanceAnimation.ModeLevel level)
        {
            foreach (var item in m_instance.m_instancingMap)
            {
                item.Value.SetModeLevel(level);
            }
        }

        public static GPUSkinning_SpawnObject AddSpawnObject(int instancingId, Transform trans)
        {
            var ue = m_instance.m_instancingMap[instancingId];
            GPUSkinning_SpawnObject obj = ue.AddSpawnObject(trans);
            obj.instancingId = instancingId;
            if (isSurvivor)
            {
                
            }
            else
            {
                obj.allAnimation = GetInstance().AllAnimation;
            }
            return obj;
        }

        public static void RemoveSpawnObject(GPUSkinning_SpawnObject spObj, int configId)
        {
            if (m_instance == null) return;
            if (spObj == null) return;
            int instancingId = spObj.instancingId;

            if (!m_instance.m_instancingMap.TryGetValue(instancingId, out var ueInstanceAnimation))
            {
                Debug.LogErrorFormat(LogModule.CitySimulation, "RemoveSpawnObject failed, model = {0} instancingId = {1}", spObj.model.name, instancingId);
                return;
            }
            
            ueInstanceAnimation.RemoveSpawnObject(spObj);
            if(ueInstanceAnimation.totalSpawnObjectsCount == 0)
                RemoveInstancingConfig(configId);
        }
        
        public static GPUInstance_SpawnObject AddGpuInstanceSpawnObject(int instancingId, Transform trans)
        {
            GPUInstance_SpawnObject obj = m_instance.m_gpuInstancingMap[instancingId].AddSpawnObject(trans);
            return obj;
        }

        public static void RemoveGpuSpawnObject(GPUInstance_SpawnObject spObj, int poolKey)
        {
            if (m_instance == null) return;
            if (spObj == null) return;
            int instancingId = spObj.instancingId;

            if (!m_instance.m_gpuInstancingMap.TryGetValue(instancingId, out var group))
            {
                return;
            }
            
            group.RemoveSpawnObject(spObj);
        }
        
        public static UEInstanceAnimation GetInstanceAnimation(GPUSkinning_SpawnObject spObj)
        {
            if (spObj == null) return null;
            int instancingId = spObj.instancingId;

            if (m_instance != null
                && m_instance.m_instancingMap.ContainsKey(instancingId))
                return m_instance.m_instancingMap[instancingId];

            return null;
        }

        public static void RemoveInstancingConfig(int configId)
        {
            if (!m_instance.m_dic.TryGetValue(configId, out int instancingId))
                return;

            m_instance.m_dic.Remove(configId);
            if (m_instance.m_instancingMap.TryGetValue(instancingId, out var ue))
            {
                ue.Dispose();
                m_instance.m_instancingMap.Remove(instancingId);
            }

            if (m_instance.m_dic.Count == 0)
            {
                if (isSurvivor)
                {
                    return;
                }
                // 销毁配置和纹理
                if (m_instance.allAnimation)
                {
#if UNITY_EDITOR

#else
                    UMTResource.UnloadAsset(m_instance.allAnimation);
#endif
                    m_instance.allAnimation = null;
                }
#if UNITY_EDITOR
                
#else
                if (m_instance.animationTex)
                {
                    Object.Destroy(m_instance.animationTex);
                    m_instance.animationTex = null;
                }
#endif
            }
        }

        /// <summary>
        /// 是否显示阴影
        /// </summary>
        public static bool IsShadowActive = true;

        private int rendererTotal;
        private int animatorTotal;

#if UNITY_EDITOR
        public void OnGUI(Rect rect)
        {
            if (drawGUI)
            {
                GUI.Label(rect, $"Gpu Skinning类型: {m_instancingMap.Count}, 渲染: {rendererTotal}, 动画: {animatorTotal}");
            }
        }

        public void OnSceneGUI()
        {
            if (drawGUI)
            {
                foreach (var item in m_instancingMap)
                {
                    var ueInstanceAnimation = item.Value;
                    ueInstanceAnimation.DrawSpawnObjects();
                }
            }
        }
#endif
        public GPUSkinning_Animation GetAnimation(string animationName)
        {
            if (AllAnimation == null)
                return null;

            foreach (var animation in AllAnimation.animations)
            {
                if(animation == null) 
                    continue;
                
                if (animation.animName == animationName)
                {
                    return animation;
                }
            }

            return null;
        }

        public int GetTotalAnimationLength()
        {
            if (AllAnimation == null)
                return 1;
            return AllAnimation.totalAnimationLength;
        }
    }
}
